﻿var spa = spa || {};

spa.dataSet = function (setName, keyPropertyName, buffer, dataContext) {
    this.DataSetName = setName;
    this.DataAdapter = new spa.dataAdapter(setName);
    this.Key = keyPropertyName;
    this.Buffer = buffer || false;
    this.Entities = {};     // Use Key for Indexer
    this.NewItems = [];     // No Valid Key, just Maintain list of items to be added
    this.PartOf = dataContext;
};

spa.dataSet.prototype = {
    CreateView: function (query) {
        /// <summary>
        /// Create a View from this DataSet
        /// </summary>
        /// <param name="controllerName" type="String">
        /// Optional: Query to use with the View
        /// </param>
        /// <return>
        /// new dataView
        /// </return>
        return new spa.dataView(this, query);
    },
    Read: function (obsArray, query, refresh) {
        /// <summary>
        /// Loads Data Into the obsArray
        /// </summary>
        /// <param name="obsArray" type="ko.observableArray">
        /// Required: Array to Load dataEntities into
        /// </param>
        /// <param name="query" type="spa.odataQuery">
        /// Required: Query to Get Entities
        /// </param>
        /// <param name="refresh" type="Boolean">
        /// Optional: Should the Query refresh entities (false to preseve changes)
        /// </param>
        var self = this;
        this.DataAdapter.QueryAll(function (data) {
            obsArray.remove(function (entity) {
                return (entity.EntityState() != spa.entityStates.added);
            });
            $.map(data, function (item) {
                var existing = self.FindByKey(item);
                if (existing) {
                    if (refresh) {
                        existing.Update(item);
                    }
                    if (existing.EntityState() != spa.entityStates.removed) {
                        obsArray.push(existing);
                    }
                }
                else {
                    var newEntity = new spa.dataEntity(item);
                    self.Attach(newEntity);
                    obsArray.push(newEntity);
                }
            });
            // TODO: Add Sorting for Additional Ordering
            if (query.orderby().length > 0) {
                obsArray.sort(function (left, right) {
                    var ordering = query.orderby()[0];
                    var fldName = ko.utils.unwrapObservable(ordering.field);
                    var l = ko.utils.unwrapObservable(left.Entity[fldName]);
                    var r = ko.utils.unwrapObservable(right.Entity[fldName]);
                    if (ordering.ascending) {
                        return l == r ? 0 : (l < r ? -1 : 1);
                    }
                    else {
                        return l == r ? 0 : (l > r ? -1 : 1);
                    }
                });
            }
        }, query.ToQueryString());
    },
    Refresh: function (entity) {
        /// <summary>
        /// Refreshes an Entity
        /// </summary>
        /// <param name="entity" type="spa.dataEntity">
        /// Required: Item to Refresh from the Server
        /// </param>
        var keyToUpdate = dataEntity.Entity[this.Key]();
        this.DataAdapter.LoadOne(keyToUpdate, function (data) {
            entity.Update(data);
        });
    },
    Create: function (entity) {
        /// <summary>
        /// Submits an Entity to the Server
        /// </summary>
        /// <param name="entity" type="spa.dataEntity">
        /// Required: Item to save (via POST)
        /// </param>
        if (entity.IsSubmitting() == false) {
            var set = this;
            entity.IsSubmitting(true);
            this.DataAdapter.PostItem(entity.ToJSON(), function (data) {
                entity.IsSubmitting(false);
                entity.Update(data);
                set.NewItems.splice(set.NewItems.indexOf(entity), 1);
                set.Attach(entity);
            });
        }
    },
    Update: function (entity) {
        /// <summary>
        /// Updates an Item to the Server
        /// </summary>
        /// <param name="entity" type="spa.dataEntity">
        /// Required: Item to Update (via PUT)
        /// </param>
        if (entity.IsSubmitting() == false) {
            var key = this.GetKey(entity.Entity);
            entity.IsSubmitting(true);
            this.DataAdapter.PutItem(key, entity.ToJSON(), function (data) {
                entity.IsSubmitting(false);
                entity.Merge(data);
            });
        }
    },
    Delete: function (entity) {
        /// <summary>
        /// Deletes an Item from the Server
        /// </summary>
        /// <param name="entity" type="spa.dataEntity">
        /// Required: Item to Delete (via DELETE)
        /// </param>
        if (entity.IsSubmitting() == false) {
            var key = this.GetKey(entity.Entity);
            var set = this;
            entity.IsSubmitting(true);
            this.DataAdapter.DeleteItem(key, function () {
                entity.IsSubmitting(false);
                set.Detach(entity);
            });
        }
    },
    Attach: function (entity) {
        /// <summary>
        /// Attaches an item to the DataSet
        /// </summary>
        /// <param name="entity" type="spa.dataEntity">
        /// Required: Item to Attach (commits immediately if Buffer is false)
        /// </param>
        var key = this.GetKey(entity.Entity);
        if (!this.Entities[key]) {
            if (!key) {
                this.NewItems.push(entity);
                if (this.Buffer === false) {
                    this.Create(entity);
                }
            }
            else {
                this.Entities[key] = entity;
                if (this.Buffer === false) {
                    // TODO: Subscribe to has changes
                    entity.EntityState.subscribe(function (newState) {
                        if (newState == spa.entityStates.modified) {
                            this.Update(entity);
                        }
                        else if (newState == spa.entityStates.removed) {
                            this.Delete(entity);
                        }
                    }, this);
                }
            }
        }
    },
    Detach: function (entity) {
        /// <summary>
        /// Removes an item from being tracked by the DataSet
        /// </summary>
        /// <param name="entity" type="spa.dataEntity">
        /// Required: Item to stop tracking (and delete from memory)
        /// </param>
        var keyToFind = entity.Entity[this.Key]();
        if (this.Entities[keyToFind]) {
            delete this.Entities[keyToFind];
        }
    },
    GetKey: function (entity) {
        /// <summary>
        /// Gets the Key associated with an entity
        /// </summary>
        /// <param name="entity" type="spa.dataEntity">
        /// Required: Entity to find the Key for
        /// </param>
        /// <return>
        /// The Key for the Entity
        /// </return>
        return ko.utils.unwrapObservable(entity[this.Key]);
    },
    FindByKey: function (entity) {
        /// <summary>
        /// Finds a Matching Entity in the set (by Key)
        /// </summary>
        /// <param name="entity" type="spa.dataEntity or JSON">
        /// Required: Entity to find the Key for
        /// </param>
        /// <return>
        /// The Entity with matching key that is attached, or null
        /// </return>
        var keyToFind = ko.utils.unwrapObservable(entity[this.Key]);
        if (this.Entities[keyToFind]) {
            return this.Entities[keyToFind];
        }
        else {
            return null;
        }
    },
    GetChanges: function () {
        /// <summary>
        /// Gets all entities which have pending updates (changes)
        /// </summary>
        /// <return>
        /// Array of dataEntities
        /// </return>
        var c = [];
        for (key in this.Entities) {
            if (this.Entities[key].HasChanges()) {
                c.push(this.Entities[key]);
            }
        }
        return c;
    },
    GetRemoved: function () {
        /// <summary>
        /// Gets all entities which have pending deletes (removed)
        /// </summary>
        /// <return>
        /// Array of dataEntities
        /// </return>
        var c = [];
        for (key in this.Entities) {
            if (this.Entities[key].EntityState() === spa.entityStates.removed) {
                c.push(this.Entities[key]);
            }
        }
        return c;
    },
    SaveAll: function () {
        /// <summary>
        /// Commits all Pending Operations (PUT, DELETE, POST)
        /// </summary>
        for (var i = 0, l = this.NewItems.length; i < l; i++) {
            this.Create(this.NewItems[i]);
        }
        var changes = this.GetChanges();
        for (var i = 0, l = changes.length; i < l; i++) {
            this.Update(changes[i]);
        }
        var deletes = this.GetRemoved();
        for (var i = 0, l = deletes.length; i < l; i++) {
            this.Delete(deletes[i]);
        }
    }
};